package org.opentcs.strategies.basic.routing.jgrapht.jjwcalg;

import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import org.jgrapht.Graph;
import org.jgrapht.graph.DirectedWeightedMultigraph;
import org.opentcs.components.kernel.routing.Edge;
import org.opentcs.components.kernel.routing.GroupMapper;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
import org.opentcs.strategies.basic.routing.PointRouterFactory;
import org.opentcs.strategies.basic.routing.ResourceAvoidanceExtractor;
import org.opentcs.strategies.basic.routing.jgrapht.DefaultModelGraphMapper;
import org.opentcs.strategies.basic.routing.jgrapht.GraphProvider;
import org.opentcs.strategies.basic.routing.jgrapht.ModelGraphMapper;

import java.util.*;

import static java.util.Objects.requireNonNull;

/**
 * 这是用来构建动态路由的graph地图的
 */
public class DyGraphProvider {
  private final TCSObjectService objectService;
  private final ModelGraphMapper defaultModelGraphMapper;
  private final GroupMapper routingGroupMapper;
  private final Map<String, GraphProvider.GraphResult> graphResultsByRoutingGroup = new HashMap<>();


  @Inject
  public DyGraphProvider(@Nonnull TCSObjectService objectService,
                       @Nonnull DefaultModelGraphMapper defaultModelGraphMapper,
                       @Nonnull GroupMapper routingGroupMapper) {
    this.objectService = requireNonNull(objectService, "objectService");
    this.defaultModelGraphMapper = requireNonNull(defaultModelGraphMapper,
        "defaultModelGraphMapper");
    this.routingGroupMapper = requireNonNull(routingGroupMapper, "routingGroupMapper");
  }


  /**
   * Invalidates any graphs that have already been calculated.
   */
  public void invalidate() {
    graphResultsByRoutingGroup.clear();
  }

  /**
   * Returns a {@link GraphProvider.GraphResult} containing the routing graph for the given vehicle.
   *
   * @param vehicle The vehicle.
   * @return A {@link GraphProvider.GraphResult} containing the routing graph for the given vehicle.
   */
  public GraphProvider.GraphResult getGraphResult(Vehicle vehicle) {
    return graphResultsByRoutingGroup.computeIfAbsent(
        routingGroupMapper.apply(vehicle),
        routingGroup -> {
          Set<Point> points = objectService.fetchObjects(Point.class);
          Set<Path> paths = objectService.fetchObjects(Path.class);
          return new GraphProvider.GraphResult(vehicle,
              points,
              paths,
              defaultModelGraphMapper.translateModel(points, paths, vehicle));
        }
    );
  }

  /**
   * Returns a {@link GraphProvider.GraphResult} that is derived from the given vehicle's "default" routing graph
   * in such a way that the given set of points is not included.
   *
   * @param vehicle         The vehicle.
   * @param pointsToExclude The set of points to not include in the derived routing graph.
   * @param pathsToExclude  The set of paths to not include in the derived routing graph.
   * @return The derived {@link GraphProvider.GraphResult}.
   */
  public GraphProvider.GraphResult getDerivedGraphResult(Vehicle vehicle,
                                                         Set<Point> pointsToExclude,
                                                         Set<Path> pathsToExclude) {
    //这是全局基本的地图（没有删除的）
    GraphProvider.GraphResult baseGraph = getGraphResult(vehicle);

    // Determine the derived point base and path base.
    Set<Point> derivedPointBase = new HashSet<>(baseGraph.getPointBase());
//    derivedPointBase.removeAll(pointsToExclude);
    Set<Path> derivedPathBase = new HashSet<>(baseGraph.getPathBase());
//    derivedPathBase.removeAll(pathsToExclude);
    Graph<String, Edge> derivedGraph = new DirectedWeightedMultigraph<>(Edge.class);


//    // Determine the vertices that should be included and add them to the derived graph.
//    Set<String> pointsToIncludeByName = derivedPointBase.stream()
//        .map(Point::getName)
//        .collect(Collectors.toSet());
//    baseGraph.getGraph().vertexSet().stream()
//        .filter(vertex -> pointsToIncludeByName.contains(vertex))
//        .forEach(vertex -> derivedGraph.addVertex(vertex));
//
//    // Determine the edges that should be included and add them to the derived graph.
//    Set<String> pathsToIncludeByName = derivedPathBase.stream()
//        .map(Path::getName)
//        .collect(Collectors.toSet());
//    baseGraph.getGraph().edgeSet().stream()
//        .filter(edge -> pathsToIncludeByName.contains(edge.getPath().getName()))
//        // Ensure that edges are only added if their source and target vertices are contained in the
//        // derived graph. This is relevant when there are points to be excluded from the derived
//        // graph, as adding an edge whose source or target vertex is not present in the graph will
//        // result in an IllegalArgumentException.
//        .filter(
//            edge -> pointsToIncludeByName.contains(edge.getSourceVertex())
//                && pointsToIncludeByName.contains(edge.getTargetVertex())
//        )
//        .forEach(edge -> {
//          derivedGraph.addEdge(edge.getSourceVertex(), edge.getTargetVertex(), edge);
//          //动态添加权重
//          double edgeWeight = baseGraph.getGraph().getEdgeWeight(edge);
//          if (pointsToExclude.stream().anyMatch(varPoint -> varPoint.getName().equalsIgnoreCase(edge.getSourceVertex())
//              || varPoint.getName().equalsIgnoreCase(edge.getTargetVertex())))
//            edgeWeight += 5000;
//          if (pathsToExclude.stream().anyMatch(varPath -> varPath.getName().equalsIgnoreCase(edge.getPath().getName())))
//            edgeWeight += 5000;
//          derivedGraph.setEdgeWeight(edge, edgeWeight);
//        });


    derivedPointBase.forEach(varPoint -> derivedGraph.addVertex(varPoint.getName()));
    Set<String> reversePathNames = new HashSet<>();
    //找出反向路径的数据
    pathsToExclude.forEach(varPath -> {
      String src_des = varPath.getSourcePoint().getName();
      String des_src = varPath.getDestinationPoint().getName();
      Optional<Path> first = derivedPathBase.stream().filter((varVarPath) -> varVarPath.getSourcePoint().getName().equalsIgnoreCase(des_src) &&
          varVarPath.getDestinationPoint().getName().equalsIgnoreCase(src_des)).findFirst();
      first.ifPresent(path -> reversePathNames.add(path.getName()));
    });
    baseGraph.getGraph().edgeSet().forEach(edge -> {
      derivedGraph.addEdge(edge.getSourceVertex(), edge.getTargetVertex(), edge);
      //动态添加权重
      double edgeWeight = baseGraph.getGraph().getEdgeWeight(edge);
      //处理点
      if (pointsToExclude.stream().anyMatch(varPoint -> varPoint.getName().equalsIgnoreCase(edge.getSourceVertex())
          || varPoint.getName().equalsIgnoreCase(edge.getTargetVertex())))
        edgeWeight += 1000;
      //处理路径
      long count = pathsToExclude.stream().filter(varPath -> varPath.getName().equalsIgnoreCase(edge.getPath().getName())).count();
      if (count > 0 && count < 3)
        edgeWeight += 1000;
      else if (count > 3 && count < 6)
        edgeWeight += 5000;
      else if (count > 6)
        edgeWeight += 8000;
      //如果反向，那么直接午饭进行直行
      if (reversePathNames.contains(edge.getPath().getName()))
        edgeWeight += 100000;
      derivedGraph.setEdgeWeight(edge, edgeWeight);
    });

    return new GraphProvider.GraphResult(vehicle, derivedPointBase, derivedPathBase, derivedGraph);
  }

  /**
   * Updates any {@link GraphProvider.GraphResult}s that have already been calculated using the given paths.
   *
   * @param paths The paths to use for the update.
   */
  public void updateGraphResults(@Nonnull Collection<Path> paths) {
    requireNonNull(paths, "paths");

    if (paths.isEmpty() || graphResultsByRoutingGroup.isEmpty()) {
      return;
    }

    for (String routingGroup : Set.copyOf(graphResultsByRoutingGroup.keySet())) {
      GraphProvider.GraphResult graphResult = graphResultsByRoutingGroup.get(routingGroup);

      // Ensure the new graph result's path base is up-to-date.
      Set<Path> updatedPathBase = new HashSet<>(graphResult.getPathBase());
      updatedPathBase.removeAll(paths);
      updatedPathBase.addAll(paths);

      graphResultsByRoutingGroup.put(
          routingGroup,
          new GraphProvider.GraphResult(
              graphResult.getVehicle(),
              graphResult.getPointBase(),
              updatedPathBase,
              defaultModelGraphMapper.updateGraph(paths,
                  graphResult.getVehicle(),
                  graphResult.getGraph())
          )
      );
    }
  }

  /**
   * Contains the result of a graph computation.
   */
  public static class GraphResult {

    private final Vehicle vehicle;
    private final Set<Point> pointBase;
    private final Set<Path> pathBase;
    private final Graph<String, Edge> graph;

    /**
     * Creates a new instance.
     *
     * @param vehicle   The vehicle for which the given graph was computed.
     * @param pointBase The set of points that was used to compute the given graph.
     * @param pathBase  The set of paths that was used to compute the given graph.
     * @param graph     The computed graph.
     */
    public GraphResult(Vehicle vehicle,
                       Set<Point> pointBase,
                       Set<Path> pathBase,
                       Graph<String, Edge> graph) {
      this.pointBase = requireNonNull(pointBase, "pointBase");
      this.pathBase = requireNonNull(pathBase, "pathBase");
      this.graph = requireNonNull(graph, "graph");
      this.vehicle = requireNonNull(vehicle, "vehicle");
    }

    /**
     * Returns the vehicle for which the graph was computed.
     *
     * @return The vehicle for which the graph was computed.
     */
    public Vehicle getVehicle() {
      return vehicle;
    }

    /**
     * Returns the set of points that was used to compute the graph.
     *
     * @return The set of points that was used to compute the graph.
     */
    public Set<Point> getPointBase() {
      return pointBase;
    }

    /**
     * Returns the set of paths that was used to compute the graph.
     *
     * @return The set of paths that was used to compute the graph.
     */
    public Set<Path> getPathBase() {
      return pathBase;
    }

    /**
     * Returns the computed graph.
     *
     * @return The computed graph.
     */
    public Graph<String, Edge> getGraph() {
      return graph;
    }
  }
}
